(function(fn) {


  if (typeof require === 'function' && typeof module === 'object' && module) //兼容cmd
  {
    fn(module.exports);
  }
  else if (typeof define === 'function' && define.amd) //兼容amd
  {
    define(function (require, exports, module) {

      fn(exports);
    });
  }
  else //普通浏览器环境
  {
    fn(window);
  }


})(function (exports) {


  'use strict'


  // 注册的Spirte类型集合
  var registry_sprites = Object.create(null);


  // 定义属性方法
  var defineProperty = Object.defineProperty;


  // 类继承方法
  Object.extend = function (fn) {

    var base = this.prototype,
      prototype = Object.create(base);

    function Class() {

      var ctor;

      if (ctor = Class.ctor)
      {
        ctor.apply(this, arguments);
      }
    };

    Class.prototype = prototype;
    Class.superclass = this;
    Class.base = base;

    prototype.Class = prototype.constructor = Class;

    fn.call(prototype, Class, base);

    Class.extend = this.extend;

    if (!Class.ctor && (fn = this.ctor))
    {
      Class.ctor = fn;
    }

    if (!Class.registry && (fn = this.registry))
    {
      Class.registry = fn;
    }

    return Class;
  };




  // 贝塞尔曲线函数集
  var bezier = (function () {


    var fn = Object.create(null);


    // 获取带控制点的贝塞尔曲线坐标集合
    fn.controls = function (points) {

      var list = [],
        length = points.length,
        loop = (length >> 1) - 1,
        k = 0.25,  // 曲线因子(影响控制线长度)
        index,
        x1,
        y1,
        x2,
        y2;

      for (var i = 0; i < loop; i++)
      {
        index = i << 1;

        list.push(points[index], points[index + 1]);

        // 第一个控制点
        if (index > 1)
        {
          x1 = points[index] + (points[index + 2] - points[index - 2]) * k;
          y1 = points[index + 1] + (points[index + 3] - points[index - 1]) * k;
        }
        else // 开始的控制点特殊处理
        {
          x1 = points[0] + (points[2] - points[0]) * k;
          y1 = points[1] + (points[3] - points[1]) * k;
        }

        // 第二个控制点
        if (index < points.length - 4)
        {
          x2 = points[index + 2] - (points[index + 4] - points[index]) * k;
          y2 = points[index + 3] - (points[index + 5] - points[index + 1]) * k;
        }
        else // 后面两个节点特殊处理
        {
          index = points.length - 2;

          x2 = points[index] - (points[index] - points[index - 2]) * k;
          y2 = points[index + 1] - (points[index + 1] - points[index - 1]) * k;
        }

        list.push(x1, y1, x2, y2);
      }

      list.push(points[length - 2], points[length - 1]);

      return list;
    };


    // 获取贝塞尔曲线指定位置的坐标
    fn.segments = function (points, index, count, list, first) {

      var x1 = points[index++],
        y1 = points[index++],
        x2 = points[index++],
        y2 = points[index++],
        x3 = points[index++],
        y3 = points[index++],
        x4 = points[index++],
        y4 = points[index],
        cx = 3 * (x2 - x1),
        cy = 3 * (y2 - y1),
        bx = 3 * (x3 - x2) - cx,
        by = 3 * (y3 - y2) - cy,
        ax = x4 - x1 - cx - bx,
        ay = y4 - y1 - cy - by,
        t = 1 / count;

      list = list || [];

      if (first !== false)
      {
        list.push(x1, y1);
      }

      for (var i = 1; i < count; i++)
      {
        var t1 = t * i,
          t2 = t1 * t1,
          t3 = t2 * t1;

        list.push(
          ax * t3 + bx * t2 + cx * t1 + x1,
          ay * t3 + by * t2 + cy * t1 + y1);
      }

      list.push(x4, y4);

      return list;
    };


    // 按指定段长近似拆分贝塞尔曲线(注：不是准确的段长)
    fn.split = function (points, size) {

      var sqrt = Math.sqrt,
        list = [points[0], points[1]],
        index = 0,
        length = points.length - 2;

      // 先计算出曲线上的相关坐标点
      while (index < length)
      {
        // 估算出大概分段数
        var dx = points[index + 6] - points[index],
          dy = points[index + 7] - points[index + 1],
          distance = sqrt(dx * dx + dy * dy),
          segments = distance / size | 0;

        fn.segments(points, index, segments, list, false);
        index += 6;
      }

      return list;
    };


    return fn;


  })();



  // 命中检测函数集
  var hitTest = (function () {


    var fn = Object.create(null);

    var sqrt = Math.sqrt;


    // 检测指定的点是否在矩形内
    fn.rect = function (x, y, left, top, width, height) {

      return x >= left && y >= top && x <= left + width && y <= top + height;
    };


    // 检测指定的点是否在圆形内
    fn.cycle = function (x, y, left, top, radius) {

      x -= left;
      y -= top;

      return sqrt(x * x + y * y) <= radius;
    };


    // 检测指定的坐标是否在多边形中
    // 使用水平射线法, 与向右的水平射线相交的节点数为单数则表示点在多边形内
    fn.polygon = function (x, y, points, index, length) {

      if (index === void 0)
      {
        index = 0;
        length = points.length;
      }

      var flag = false,
        x1 = points[length - 2] | 0,
        y1 = points[length - 1] | 0,
        x2,
        y2;

      if (x === x1 && y === y1)
      {
        return true;
      }

      while (index < length)
      {
        x2 = points[index++] | 0;
        y2 = points[index++] | 0;

        if (x === x2 && y === y2)
        {
          return true;
        }

        // 判断线段两端点的y坐标是否在射线两侧
        if (y1 >= y && y2 <= y || y1 <= y && y2 >= y)
        {
          // 线段上与射线Y坐标相同的点的X坐标
          x1 += (y - y1) * (x2 - x1) / (y2 - y1) | 0;

          if (x1 === x)
          {
            return true;
          }

          if (x1 > x)
          {
            flag = !flag;
          }
        }

        x1 = x2;
        y1 = y2;
      }

      return flag;
    };


    // 检测指定的点是否接近直线
    fn.line = function (x, y, points, offset) {

      var PI = Math.PI / 2,
        sqrt = Math.sqrt,
        acos = Math.acos,
        sin = Math.sin,
        index = 2,
        length = points.length,
        x1 = points[0],
        y1 = points[1],
        x2,
        y2,
        ab,
        ax,
        bx,
        any;

      while (index < length)
      {
        x2 = points[index++];
        y2 = points[index++];

        ax = sqrt((any = x - x1) * any + (any = y - y1) * any);

        if (ax <= offset)
        {
          return true;
        }

        bx = sqrt((any = x - x2) * any + (any = y - y2) * any);

        if (bx <= offset)
        {
          return true;
        }

        ab = sqrt((any = x2 - x1) * any + (any = y2 - y1) * any);

        // 通过余弦定理求角度,如果点与线段的端点小于90度,则垂线为最短距离,否则对应的边线为最短距离
        if ((any = acos((ax * ax + ab * ab - bx * bx) / (2 * ax * ab))) < PI &&
          acos((bx * bx + ab * ab - ax * ax) / (2 * bx * ab)) < PI)
        {
          // 检测垂线距离
          if (ax * sin(any) <= offset)
          {
            return true;
          }
        }

        x1 = x2;
        y1 = y2;
      }

      return false;
    };


    // 检测指定的点是否在贝塞乐曲线上
    fn.bezier = function (x, y, points) {

      var index = 0,
        length = points.length,
        polygon = fn.polygon,
        split = bezier.split,
        line = fn.line;

      while (index < length)
      {
        var list = split(points, 20);

        if (line(x, y, list, 6))
        {
          return true;
        }

        index += 6;
      }

      return false;
    };


    return fn;


  })();



  /**
   * 精灵基类
   */
  var Sprite = Object.extend(function (Class) {



    var id = 1;


    /**
     * Id
     */
    defineProperty(this, 'id', {

      get: function () {

        return this.__id || (this.__id = id++);
      }
    });


    /**
     * 文字内容
     */
    defineProperty(this, 'text', {

      get: function () {

        return this.__text || this.__text_ || '';
      },

      set: function (value) {

        this.__text = '' + value;
      }
    });


    /**
     * 边框线宽度
     */
    defineProperty(this, 'borderWidth', {

      get: function () {

        return this.__borderWidth || 2;
      },

      set: function (value) {

        this.__borderWidth = value | 0;
      }
    });


    /**
     * 边框颜色
     */
    defineProperty(this, 'borderColor', {

      get: function () {

        return this.__borderColor || 'transparent';
      },

      set: function (value) {

        this.__borderColor = '' + value;
      }
    });


    /**
     * 背景颜色
     */
    defineProperty(this, 'backgroundColor', {

      get: function () {

        return this.__backgroundColor || '';
      },

      set: function (value) {

        this.__backgroundColor = '' + value;
      }
    });


    /**
     * 字体颜色
     */
    defineProperty(this, 'color', {

      get: function () {

        return this.__color || 'white';
      },

      set: function (value) {

        this.__color = '' + value;
      }
    });


    /**
     * 是否可见
     */
    defineProperty(this, 'visible', {

      get: function () {

        return this.__visible === false ? false : true;
      },

      set: function (value) {

        this.__visible = !!value;
      }
    });



    this.render = function (context, x, y) {

    };


    this.renderSelected = function (context, x, y) {


    };


    this.hitTest = function (x, y, left, top) {

    };



    this.location = function (frame) {

      return frame.points[this.id].slice(0);
    };


    this.dragMove = function (start, distanceX, distanceY, frame) {

      var points = frame.points[this.id];

      points[0] = start[0] + distanceX;
      points[1] = start[1] + distanceY;

      return points;
    };



    this.serialize = function (array) {

      var any;

      array.push('"id":', this.id, ',"sprite":"', this.typeName, '"');

      if (any = this.__text)
      {
        array.push(',"text":"', any, '"');
      }

      if (any = this.__borderWidth)
      {
        array.push(',"borderWidth":', any);
      }

      if (any = this.__borderColor)
      {
        array.push(',"borderColor":"', any, '"');
      }

      if (any = this.__backgroundColor)
      {
        array.push(',"backgroundColor":"', any, '"');
      }

      if (any = this.__color)
      {
        array.push(',"color":"', any, '"');
      }

      if (this.__visible === false)
      {
        array.push(',"visible":false');
      }
    };


    this.deserialize = function (data) {

      for (var name in data)
      {
        switch (name)
        {
          case 'id':
            if ((this.__id = data[name]) >= id)
            {
              id++;
            }
            break;

          case 'sprite':
            break;

          default:
            this[name] = data[name];
            break;
        }
      }
    };



    Class.registry = function (name) {

      registry_sprites[this.prototype.typeName = name] = this;
    };


  });



  /**
   * 玩家基类
   */
  var Player = Sprite.extend(function (Class) {



    this.__text_ = '1';



    /**
     * 形状 1,2,3,4,5,6
     */
    defineProperty(this, 'shape', {

      get: function () {

        return this.__shape || this.__shape_ || 1;
      },

      set: function (value) {

        this.__shape = value | 0;
      }
    });



    this.render = function (context, x, y) {

      context.save();

      context.strokeStyle = this.borderColor;

      if (!(context.fillStyle = this.backgroundColor))
      {
        this.renderStyle(context, x, y);
      }

      context.textAlign = 'center';
      context.textBaseline = 'middle';
      context.font = '24px Arial';

      if ((context.lineWidth = this.borderWidth) & 1)
      {
        context.translate(0.5, 0.5);
      }

      context.beginPath();

      switch (this.shape)
      {
        case 1:
          context.arc(x, y, 18, 0, Math.PI * 2);
          context.fill();

          context.fillStyle = this.color;
          context.fillText(this.text, x, y);

          context.stroke();
          break;

        case 2:
          context.moveTo(x, y - 18);
          context.lineTo(x - 18, y + 18);
          context.lineTo(x + 18, y + 18);
          context.closePath();

          context.fill();

          context.fillStyle = this.color;
          context.fillText(this.text, x, y + 5);

          context.stroke();
          break;

        case 3:
          context.strokeStyle = context.fillStyle;

          context.moveTo(x - 14, y - 14);
          context.lineTo(x + 7, y + 7);

          context.moveTo(x + 14, y - 14);
          context.lineTo(x - 14, y + 14);

          context.stroke();

          context.fillText(this.text, x + 13, y + 13);
          break;

        case 5:
          context.rect(x - 18, y - 18, 36, 36);
          context.fill();

          context.fillStyle = this.color;
          context.fillText(this.text, x, y);

          context.stroke();
          break;

        case 6:
          context.arc(x, y, 12, 0, Math.PI * 2);
          context.fill();

          context.strokeStyle = context.fillStyle;
          context.beginPath();

          context.arc(x, y - 16, 20, Math.PI / 8, 7 * Math.PI / 8);

          context.lineWidth = 6;
          context.stroke();

          context.fillText(this.text, x + 16, y + 16);
          break;

        default:
          this.__render_shape(context, x, y);
          break;
      }

      context.restore();
    };


    this.renderStyle = function (context, x, y) {

    };


    this.renderSelected = function (context, x, y) {

      var x1, y1, x2, y2;

      switch (this.shape)
      {
        case 1:
        case 2:
        case 3:
        case 5:
          x1 = x - 18;
          y1 = y - 18;
          x2 = x + 18;
          y2 = y + 18;
          break;

        case 4:
          x1 = x - 21;
          y1 = y - 25;
          x2 = x + 21;
          y2 = y + 25;
          break;

        case 6:
          x1 = x - 20;
          y1 = y - 12;
          x2 = x + 20;
          y2 = y + 25;
          break;
      }

      context.fillStyle = 'red';

      context.fillRect(x1 - 3, y1 - 3, 6, 6);
      context.fillRect(x2 - 3, y1 - 3, 6, 6);
      context.fillRect(x1 - 3, y2 - 3, 6, 6);
      context.fillRect(x2 - 3, y2 - 3, 6, 6);
    };



    this.hitTest = function (x, y, left, top) {

      switch (this.shape)
      {
        case 1:
          return hitTest.cycle(x, y, left, top, 18);

        case 2:
          return hitTest.polygon(x, y, [left, top - 18, left - 18, top + 18, left + 18, top + 18]);

        case 4:
          return hitTest.rect(x, y, left - 18, top - 22, 36, 44);

        default:
          return hitTest.rect(x, y, left - 18, top - 18, 36, 36);
      }
    };


    this.serialize = function (array) {

      var any;

      Class.base.serialize.call(this, array);

      if (any = this.__shape)
      {
        array.push(',"shape":"', any, '"');
      }
    };


  });



  /**
   * 进攻玩家类
   */
  Player.extend(function (Class) {


    this.__shape_ = 1;


    this.__text_ = '1';



    this.renderStyle = function (context, x, y) {

      var style = context.createLinearGradient(x - 18, y - 22, x + 18, y + 22);

      style.addColorStop(0.3, '#53CBFE');
      style.addColorStop(0.8, '#3AA8FC');

      context.fillStyle = style;
    };


    this.__render_shape = function (context, x, y) {

      context.moveTo(x - 14, y - 16);
      context.lineTo(x - 5, y - 22);
      context.lineTo(x + 5, y - 22);
      context.lineTo(x + 14, y - 16);
      context.lineTo(x + 14, y + 12);
      context.lineTo(x - 14, y + 12);
      context.closePath();

      context.fill();

      context.beginPath();

      context.moveTo(x - 17.5, y - 4);
      context.lineTo(x - 17.5, y + 22);

      context.moveTo(x + 17.5, y - 4);
      context.lineTo(x + 17.5, y + 22);

      context.lineWidth = 3;
      context.strokeStyle = context.fillStyle;
      context.stroke();
    };


  }).registry('offense');



  /**
   * 防守玩家类
   */
  Player.extend(function (Class) {


    this.__shape_ = 2;


    this.__text_ = '1';


    this.renderStyle = function (context, x, y) {

      var style = context.createLinearGradient(x - 18, y - 22, x + 18, y + 22);

      style.addColorStop(0.3, '#70E085');
      style.addColorStop(0.8, '#3BC759');

      context.fillStyle = style;
    };


    this.__render_shape = function (context, x, y) {

      context.moveTo(x - 14, y - 22);
      context.lineTo(x + 14, y - 22);
      context.lineTo(x + 14, y + 12);
      context.lineTo(x - 14, y + 12);
      context.closePath();

      context.fill();

      context.beginPath();

      context.moveTo(x - 17.5, y - 4);
      context.lineTo(x - 17.5, y + 22);

      context.moveTo(x + 17.5, y - 4);
      context.lineTo(x + 17.5, y + 22);

      context.lineWidth = 3;
      context.strokeStyle = context.fillStyle;
      context.stroke();
    };


  }).registry('defense');



  /**
   * 球类
   */
  Sprite.extend(function (Class) {


    this.render = function (context, x, y) {

      context.beginPath();
      context.arc(x, y, 14, 0, Math.PI * 2);

      if (!(context.fillStyle = this.backgroundColor))
      {
        var style = context.createLinearGradient(x - 15, y - 15, x + 15, y + 15);

        style.addColorStop(0.3, '#F8A845');
        style.addColorStop(0.8, '#FF8E01');

        context.fillStyle = style;
      }

      context.fill();

      context.strokeStyle = this.borderColor;
      context.lineWidth = this.borderWidth;
      context.stroke();
    };


    this.renderSelected = function (context, x, y) {

      context.fillStyle = 'red';

      context.fillRect(x - 14 - 3, y - 14 - 3, 6, 6);
      context.fillRect(x + 14 - 3, y - 14 - 3, 6, 6);
      context.fillRect(x - 14 - 3, y + 14 - 3, 6, 6);
      context.fillRect(x + 14 - 3, y + 14 - 3, 6, 6);
    };



    this.hitTest = function (x, y, left, top) {

      return hitTest.cycle(x, y, left, top, 10);
    };


  }).registry('ball');



  /**
   * 障碍物类
   */
  Sprite.extend(function (Class) {


    this.render = function (context, x, y) {

      context.beginPath();

      context.moveTo(x, y - 19);
      context.lineTo(x - 12, y + 15);
      context.lineTo(x - 16, y + 15);
      context.lineTo(x - 16, y + 19);
      context.lineTo(x + 16, y + 19);
      context.lineTo(x + 16, y + 15);
      context.lineTo(x + 12, y + 15);
      context.closePath();

      if (!(context.fillStyle = this.backgroundColor))
      {
        var style = context.createLinearGradient(x - 16, y - 19, x + 16, y + 19);

        style.addColorStop(0.3, '#F8A845');
        style.addColorStop(0.8, '#FF8E01');

        context.fillStyle = style;
      }

      context.fill();

      context.strokeStyle = this.borderColor;
      context.lineWidth = this.borderWidth;
      context.stroke();
    };


    this.renderSelected = function (context, x, y) {

      context.fillStyle = 'red';

      context.fillRect(x - 20, y - 20, 6, 6);
      context.fillRect(x + 15, y - 20, 6, 6);
      context.fillRect(x - 20, y + 20, 6, 6);
      context.fillRect(x + 15, y + 20, 6, 6);
    };



    this.hitTest = function (x, y, left, top) {

      return hitTest.polygon(x, y, [
        left, top - 19,
        left - 12, top + 15,
        left - 16, top + 15,
        left - 16, top + 19,
        left + 16, top + 19,
        left + 16, top + 15,
        left + 12, top + 15
      ]);
    };


  }).registry('obstacle');



  /**
   * 线条类
   */
  var Line = Object.extend(function (Class) {



    // 线型绘制集合
    var shapes = Object.create(null);



    Class.ctor = function () {

      this.points = [];
    };



    // 类型名称
    this.typeName = 'line';



    /**
     * 线条形状 l1,l2,l3,l4,l5 c1,c2,c3,c4,c5
     */
    defineProperty(this, 'shape', {

      get: function () {

        return this.__shape || 'l1';
      },

      set: function (value) {

        this.__shape = '' + value;
      }
    });


    /**
     * 是否显示箭头
     */
    defineProperty(this, 'arrow', {

      get: function () {

        return this.__arrow || false;
      },

      set: function (value) {

        this.__arrow = !!value;
      }
    });


    /**
     * 线条粗细
     */
    defineProperty(this, 'thick', {

      get: function () {

        return this.__thick || 2;
      },

      set: function (value) {

        this.__thick = value | 0;
      }
    });


    /**
     * 线条颜色
     */
    defineProperty(this, 'color', {

      get: function () {

        return this.__color || 'black';
      },

      set: function (value) {

        this.__color = '' + value;
      }
    });



    this.render = function (context) {

      var fn = shapes[this.shape];

      if (fn)
      {
        context.lineWidth = this.thick;
        context.strokeStyle = this.color;

        fn(context, this.points);
      }
    };


    this.renderSelected = function (context, x, y) {

      var points = this.points,
        index = 0,
        length = points.length;

      context.fillStyle = 'red';

      while (index < length)
      {
        var x = points[index++],
          y = points[index++];

        context.fillRect(x - 5, y - 5, 10, 10);
      }
    };



    // 画虚直线
    function dash_line(context, points) {

      var x1 = points[0],
        y1 = points[1],
        index = 2,
        length = points.length;

      context.beginPath();
      context.moveTo(x1, y1);

      while (index < length)
      {
        var x2 = points[index++],
          y2 = points[index++],
          dx = x2 - x1,
          dy = y2 - y1,
          distance = Math.sqrt(dx * dx + dy * dy),
          angle = Math.atan(dy / dx),
          dx1 = Math.cos(angle),
          dy1 = Math.sin(angle),
          dx2 = dx1 * 5,
          dy2 = dy1 * 5;

        dx1 *= 8;
        dy1 *= 8;

        while (distance > 0)
        {
          if ((distance -= 8) <= 0)
          {
            context.lineTo(x2, y2);
            break;
          }

          context.lineTo(x1 += dx1, y1 += dy1);

          if ((distance -= 5) <= 0)
          {
            break;
          }

          context.moveTo(x1 += dx2, y1 += dy2);
        }
      }

      context.stroke();
    };


    // 获取坐标点的等距偏移坐标
    function points_offset(points, offset) {

      var atan = Math.atan,
        sin = Math.sin,
        cos = Math.cos,
        length = (points.length >> 1) << 1,
        list = new Array(length),
        index = 0,
        i = 2,
        x1 = points[0],
        y1 = points[1];

      while (i < length)
      {
        var x2 = points[i++],
          y2 = points[i++],
          angle = atan((y2 - y1) / (x2 - x1)),
          dx = offset * sin(angle),
          dy = offset * cos(angle);

        if (x2 >= x1 && y2 < y1 || x1 <= x2 && y2 > y1)
        {
          dx = -dx;
        }

        if (x2 < x1)
        {
          dy = -dy;
        }

        if (i > 4)
        {
          cross_point(
            list[index - 4], list[index - 3], list[index - 2], list[index - 1],
            x1 + dx, y1 + dy, x2 + dx, y2 + dy,
            list, index - 2);
        }
        else
        {
          list[index++] = x1 + dx;
          list[index++] = y1 + dy;
        }

        list[index++] = x2 + dx;
        list[index++] = y2 + dy;

        x1 = x2;
        y1 = y2;
      }

      return list;
    };


    // 获取线段的交点坐标
    function cross_point(ax, ay, bx, by, cx, cy, dx, dy, list, index) {

      // 三角形abc面积的2倍
      var abc = (ax - cx) * (by - cy) - (ay - cy) * (bx - cx);

      // 三角形abd面积的2倍
      var abd = (ax - dx) * (by - dy) - (ay - dy) * (bx - dx);

      // 三角形cda面积的2倍
      var cda = (cx - ax) * (dy - ay) - (cy - ay) * (dx - ax);

      // 三角形cdb面积的2倍
      // 不需要再用公式计算面积, 而是通过已知的三个面积加减得出.
      var cdb = cda + abc - abd;

      //计算交点坐标
      var t = cda / (abd - abc);

      ax += t * (bx - ax);
      ay += t * (by - ay);

      if (list)
      {
        list[index] = ax;
        list[index + 1] = ay;
      }

      return [ax, ay];
    };


    // 画半圆直线
    function cycle_line(context, x1, y1, x2, y2) {

      var dx = x2 - x1,
        dy = y2 - y1,
        index = 0,
        length = (Math.sqrt(dx * dx + dy * dy) - 16 | 0) >> 3,
        PI = Math.PI / 2,
        sin = Math.sin,
        cos = Math.cos,
        angle,
        dx1,
        dy1,
        dx2,
        dy2;

      context.beginPath();
      context.moveTo(x1, y1);

      if (length > 0)
      {
        angle = Math.atan(dy / dx);

        dx1 = cos(angle) * 8;
        dy1 = sin(angle) * 8;

        dx2 = cos(PI + angle) * 6;
        dy2 = sin(PI + angle) * 6;

        // 调整角度
        if (dx < 0 && dx1 > 0 || dx > 0 && dx1 < 0)
        {
          dx1 = -dx1;
          dx2 = -dx2;
        }

        if (dy > 0 && dy1 < 0 || dy < 0 && dy1 > 0)
        {
          dy1 = -dy1;
          dy2 = -dy2;
        }

        context.lineTo(x1 += dx1, y1 += dy1);

        while (index++ < length)
        {
          // 绘制上半圆
          context.bezierCurveTo(x1 - dx2, y1 - dy2, (x1 += dx1) - dx2, (y1 += dy1) - dy2, x1, y1);

          if (index++ < length)
          {
            // 绘制下半圆
            context.bezierCurveTo(x1 + dx2, y1 + dy2, (x1 += dx1) + dx2, (y1 += dy1) + dy2, x1, y1);
          }
        }
      }

      context.lineTo(x2, y2);
      context.stroke();
    };


    // 结束箭头
    function arrow_end(context, points) {

      var index = points.length - 1,
        x1 = points[index - 3],
        y1 = points[index - 2],
        x2 = points[index - 1],
        y2 = points[index],
        dx = x2 - x1,
        dy = y2 - y1,
        sin = Math.sin,
        cos = Math.cos,
        angle;

      context.fillStyle = context.strokeStyle;
      context.beginPath();

      if (Math.sqrt(dx * dx + dy * dy) < 2)
      {
        x1 = points[index - 5];
        y1 = points[index - 4];

        dx = x2 - x1;
        dy = y2 - y1;
      }

      angle = Math.atan(dy / dx);

      dx = cos(angle);
      dy = sin(angle);

      if (x2 < x1)
      {
        dx = -dx;
        dy = -dy;
      }

      context.moveTo(x2 + dx * 12, y2 + dy * 12);

      x1 = x2 - dx * 5;
      y1 = y2 - dy * 5;

      angle += Math.PI / 2;

      dx = cos(angle) * 8;
      dy = sin(angle) * 8;

      if (x2 < x1)
      {
        dx = -dx;
        dy = -dy;
      }

      context.lineTo(x1 + dx, y1 + dy);
      context.lineTo(x2, y2);
      context.lineTo(x1 - dx, y1 - dy);
      context.closePath();

      context.fill();
    };


    // 结束线段
    function line_end(context, points) {

      var index = points.length - 4,
        x1 = points[index++],
        y1 = points[index++],
        x2 = points[index++],
        y2 = points[index],
        dx = x2 - x1,
        dy = y2 - y1,
        angle = Math.atan(dy / dx);

      angle += Math.PI / 2;

      dx = Math.cos(angle) * 12;
      dy = Math.sin(angle) * 12;

      context.beginPath();

      context.moveTo(x2 - dx, y2 - dy);

      context.lineTo(x2 + dx, y2 + dy);

      context.stroke();
    };


    // 加虚曲线
    function dash_curve(context, points) {

      var sqrt = Math.sqrt,
        size = 8,
        distance = 0,
        index = 2,
        x1,
        y1,
        x2,
        y2,
        length;

      points = bezier.split(points, 2);

      length = points.length - 2;

      context.beginPath();

      context.moveTo(x1 = points[0], y1 = points[1]);

      while (index < length)
      {
        x2 = points[index++];
        y2 = points[index++];

        x1 = x2 - x1;
        y1 = y2 - y1;

        distance += sqrt(x1 * x1 + y1 * y1);

        if (distance >= size)
        {
          if (size === 8)
          {
            size = 3;
            context.lineTo(x2, y2);
          }
          else
          {
            size = 8;
            context.moveTo(x2, y2);
          }

          distance = 0;
        }

        x1 = x2;
        y1 = y2;
      }

      context.lineTo(points[index++], points[index]);

      context.stroke();
    };


    // 画半圆曲线
    function cycle_curve(context, points) {

      var PI = Math.PI / 2,
        sqrt = Math.sqrt,
        atan = Math.atan,
        sin = Math.sin,
        cos = Math.cos,
        index = 4,
        length,
        angle,
        x1,
        y1,
        x2,
        y2,
        dx,
        dy,
        dx1,
        dy1,
        dx2,
        dy2,
        reverse;

      points = bezier.split(points, 6);

      length = points.length - 4;

      context.beginPath();

      context.moveTo(points[0], points[1]);
      context.lineTo(x1 = points[2], y1 = points[3]);

      while (index < length)
      {
        x2 = points[index++];
        y2 = points[index++];

        dx = x2 - x1;
        dy = y2 - y1;

        // 距离太近则跳过
        if (sqrt(dx * dx + dy * dy) < 3)
        {
          continue;
        }

        angle = atan(dy / dx);

        dx1 = cos(angle) * 8;
        dy1 = sin(angle) * 8;

        dx2 = cos(PI + angle) * 6;
        dy2 = sin(PI + angle) * 6;

        // 调整角度
        if (dx < 0 && dx1 > 0 || dx > 0 && dx1 < 0)
        {
          dx1 = -dx1;
          dx2 = -dx2;
        }

        if (dy > 0 && dy1 < 0 || dy < 0 && dy1 > 0)
        {
          dy1 = -dy1;
          dy2 = -dy2;
        }

        // 绘制上半圆
        if (reverse = !reverse)
        {
          context.bezierCurveTo(x1 - dx2, y1 - dy2, (x1 += dx1) - dx2, (y1 += dy1) - dy2, x1, y1);
        }
        else // 绘制下半圆
        {
          context.bezierCurveTo(x1 + dx2, y1 + dy2, (x1 += dx1) + dx2, (y1 += dy1) + dy2, x1, y1);
        }
      }

      index = points.length - 2;
      context.lineTo(points[index++], points[index]);

      context.stroke();
    };



    shapes.l1 = function (context, points, arrow) {

      var index = 2,
        length = points.length;

      context.beginPath();
      context.moveTo(points[0], points[1]);

      while (index < length)
      {
        context.lineTo(points[index++], points[index++]);
      }

      context.stroke();

      if (arrow !== false)
      {
        arrow_end(context, points);
      }
    };


    shapes.l2 = function (context, points, arrow) {

      if (context.setLineDash)
      {
        context.save();
        context.setLineDash([8, 5]);

        shapes.l1(context, points, arrow);

        context.restore();
      }
      else // 不支持虚线时特殊处理
      {
        dash_line(context, points);

        if (arrow !== false)
        {
          arrow_end(context, points);
        }
      }
    };


    shapes.l3 = function (context, points, arrow) {

      var index = 2,
        length = points.length,
        x1 = points[0],
        y1 = points[1],
        x2,
        y2;

      context.beginPath();
      context.moveTo(x1, y1);

      while (index < length)
      {
        x2 = points[index++];
        y2 = points[index++];

        cycle_line(context, x1, y1, x1 = x2, y1 = y2);
      }

      context.stroke();

      if (arrow !== false)
      {
        arrow_end(context, points);
      }
    };


    shapes.l4 = function (context, points, arrow) {

      shapes.l1(context, points, false);

      if (arrow !== false)
      {
        line_end(context, points);
      }
    };


    shapes.l5 = function (context, points, arrow) {

      shapes.l1(context, points_offset(points, -2), false);
      shapes.l1(context, points_offset(points, 2), false);

      if (arrow !== false)
      {
        arrow_end(context, points);
      }
    };



    shapes.c1 = function (context, points, arrow) {

      if (points.length < 5)
      {
        shapes.l1(context, points, arrow);
      }
      else
      {
        var index = 2,
          length = (points = bezier.controls(points)).length;

        context.beginPath();
        context.moveTo(points[0], points[1]);

        while (index < length)
        {
          context.bezierCurveTo(points[index++], points[index++], points[index++], points[index++], points[index++], points[index++]);
        }

        context.stroke();

        if (arrow !== false)
        {
          arrow_end(context, points);
        }
      }
    };


    shapes.c2 = function (context, points, arrow) {

      if (points.length < 5)
      {
        shapes.l2(context, points, arrow);
      }
      else if (context.setLineDash)
      {
        context.save();
        context.setLineDash([8, 5]);

        shapes.c1(context, points);

        context.restore();
      }
      else // 不支持虚线时特殊处理
      {
        dash_curve(context, points = bezier.controls(points));

        if (arrow !== false)
        {
          arrow_end(context, points);
        }
      }
    };


    shapes.c3 = function (context, points, arrow) {

      if (points.length < 5)
      {
        shapes.l3(context, points, arrow);
      }
      else
      {
        cycle_curve(context, points = bezier.controls(points));

        if (arrow !== false)
        {
          arrow_end(context, points);
        }
      }
    };


    shapes.c4 = function (context, points, arrow) {

      if (points.length < 5)
      {
        shapes.l4(context, points, arrow);
      }
      else
      {
        shapes.c1(context, points, false);

        if (arrow !== false)
        {
          line_end(context, points);
        }
      }
    };


    shapes.c5 = function (context, points, arrow) {

      if (points.length < 5)
      {
        shapes.l5(context, points, arrow);
      }
      else
      {
        shapes.c1(context, points_offset(points, -2), false);
        shapes.c1(context, points_offset(points, 2), false);

        if (arrow !== false)
        {
          arrow_end(context, points);
        }
      }
    };



    this.hitTest = function (x, y) {

      var points = this.points;

      if (this.shape[0] === 'l')
      {
        return hitTest.line(x, y, points, 6);
      }

      points = points.controls || bezier.controls(points);

      return hitTest.bezier(x, y, points, 6);
    };


    // 获取指定坐标的控制点索引
    this.pointAt = function (x, y) {

      var points = this.points,
        index = 0,
        length = points.length,
        x1,
        y1;

      while (index < length)
      {
        x1 = points[index++];
        y1 = points[index++];

        if (x >= x1 - 5 && x <= x1 + 5 && y >= y1 - 5 && y <= y1 + 5)
        {
          return index - 2;
        }
      }

      return -1;
    };



    this.location = function (frame) {

      return this.points.slice(0);
    };


    this.dragMove = function (start, distanceX, distanceY, frame) {

      var points = this.points,
        index = 0,
        length = points.length;

      while (index < length)
      {
        points[index] = start[index++] + distanceX;
        points[index] = start[index++] + distanceY;
      }

      return points;
    };



    this.serialize = function (array) {

      var any;

      array.push('"shape":"', this.shape, '"');

      if (any = this.__arrow)
      {
        array.push(',"arrow":1');
      }

      if (any = this.__thick)
      {
        array.push(',"thick":1');
      }

      if (any = this.__color)
      {
        array.push(',"color":"', any, '"');
      }

      array.push(',"points":[', this.points.join(','), ']');
    };


    this.deserialize = function (data) {

      var any;

      if (any = data.shape)
      {
        this.shape = any;
      }

      if (data.arrow)
      {
        this.arrow = 1;
      }

      if (any = data.thick)
      {
        this.thick = any;
      }

      if (any = data.color)
      {
        this.color = any;
      }

      if (any = data.points)
      {
        this.points.push.apply(this.points, any);
      }
    };



  });



  /**
   * 关键帧类
   */
  var Frame = Object.extend(function (Class) {



    var indexOf = Array.prototype.indexOf;



    Class.ctor = function (stage) {

      this.stage = stage;
      this.lines = [];
      this.points = {};
    };


    /**
     * 帧索引
     */
    defineProperty(this, 'index', {

      get: function () {

        return indexOf.call(this.stage.frames, this);
      }
    });



    /**
     * 是否暂停
     */
    defineProperty(this, 'pause', {

      get: function () {

        return this.__pause || false;
      },

      set: function (value) {

        this.__pause = !!value;
      }
    });


    /**
     * 持续时间
     */
    defineProperty(this, 'duration', {

      get: function () {

        return this.__duration || 1;
      },

      set: function (value) {

        this.__duration = value | 0;
      }
    });


    /**
     * 参与人数
     */
    defineProperty(this, 'playerNumber', {

      get: function () {

        return this.__players || 0;
      },

      set: function (value) {

        this.__players = value | 0;
      }
    });


    /**
     * 球数量
     */
    defineProperty(this, 'ballNumber', {

      get: function () {

        return this.__balls || 0;
      },

      set: function (value) {

        this.__balls = value | 0;
      }
    });


    /**
     * 篮筐数
     */
    defineProperty(this, 'basketNumber', {

      get: function () {

        return this.__baskets || 0;
      },

      set: function (value) {

        this.__baskets = value | 0;
      }
    });


    /**
     * 器械数
     */
    defineProperty(this, 'equipmentNumber', {

      get: function () {

        return this.__equipments || 0;
      },

      set: function (value) {

        this.__equipments = value | 0;
      }
    });


    /**
     * 是否分组，
     */
    defineProperty(this, 'isGroup', {

      get: function () {

        return this.__group || false;
      },

      set: function (value) {

        this.__group = !!value;
      }
    });


    /**
     * 描述
     */
    defineProperty(this, 'describe', {

      get: function () {

        return this.__describe || '';
      },

      set: function (value) {

        this.__describe = '' + value;
      }
    });


    /**
     * 其它
     */
    defineProperty(this, 'other', {

      get: function () {

        return this.__other || '';
      },

      set: function (value) {

        this.__other = '' + value;
      }
    });


    /**
     * 上一帧
     */
    defineProperty(this, 'previous', {

      get: function () {

        var frames = this.stage.frames;
        return frames[indexOf.call(frames, this) - 1] || null;
      }
    });


    /**
     * 下一帧
     */
    defineProperty(this, 'next', {

      get: function () {

        var frames = this.stage.frames;
        return frames[indexOf.call(frames, this) + 1] || null;
      }
    });



    this.render = function (context, type, except) {

      var points = this.points,
        list = this.stage.sprites,
        index = 0,
        item,
        point;

      if (type & 1)
      {
        while (item = list[index++])
        {
          if (item !== except && item.visible && (point = points[item.id]))
          {
            item.render(context, point[0], point[1]);
          }
        }
      }

      if (type & 2)
      {
        list = this.lines;
        index = 0;

        while (item = list[index++])
        {
          if (item !== except)
          {
            item.render(context);
          }
        }
      }
    };


    this.animate = function (context, nextPoints, offset) {

      var points = this.points,
        list = this.stage.sprites,
        index = 0,
        item,
        point,
        any;

      while (item = list[index++])
      {
        if (point = points[any = item.id])
        {
          if (any = nextPoints[any])
          {
            item.render(context, point[0] + (any[0] - point[0]) * offset, point[1] + (any[1] - point[1]) * offset);
          }
          else
          {
            item.render(context, point[0], point[1]);
          }
        }
      }
    };



    this.serialize = function (array, keys) {

      var value = this.points,
        flag;

      array.push('"points":{');

      for (var name in value)
      {
        // 移除不存在的元素的坐标
        if (keys[name])
        {
          if (flag)
          {
            array.push(',');
          }
          else
          {
            flag = 1;
          }

          array.push('"', name, '":[', value[name].join(','), ']');
        }
      }

      array.push('},"lines":[');

      value = this.lines;

      for (var i = 0, l = value.length; i < l; i++)
      {
        array.push(i > 0 ? ',{' : '{');

        value[i].serialize(array);

        array.push('}');
      }

      array.push(']');

      if (this.__pause)
      {
        array.push(',"pause":1');
      }

      if (this.__group)
      {
        array.push(',"isGroup":1');
      }

      if (value = this.__duration)
      {
        array.push(',"duration":', value);
      }

      if (value = this.__players)
      {
        array.push(',"playerNumber":', value);
      }

      if (value = this.__balls)
      {
        array.push(',"ballNumber":', value);
      }

      if (value = this.__baskets)
      {
        array.push(',"basketNumber":', value);
      }

      if (value = this.__equipments)
      {
        array.push(',"equipmentNumber":', value);
      }

      if (value = this.__describe)
      {
        array.push(',"describe":"', value.replace(/"/g, '\\"'), '"');
      }

      if (value = this.__other)
      {
        array.push(',"other":"', value.replace(/"/g, '\\"'), '"');
      }
    };


    this.deserialize = function (data) {

      for (var name in data)
      {
        var value = data[name];

        switch (name)
        {
          case 'points':
            this.points = value;
            break;

          case 'lines':
            if (value && value[0])
            {
              var lines = this.lines;

              for (var i = 0, l = value.length; i < l; i++)
              {
                var line = new Line();

                line.deserialize(value[i]);
                lines.push(line);
              }
            }
            break;

          default:
            this[name] = value;
            break;
        }
      }
    };



    this.dispose = function () {

      this.points.length = this.lines.length = 0;
      this.stage = this.sprites = null;
    };


  });




  /**
   * 舞台类
   */
  var Stage = Object.extend(function (Class) {



    // 鼠标是否按下
    var mousedown = {};


    // 正在添加的线条
    var addingLine = null;


    // 数组项删除或插入
    var splice = Array.prototype.splice;




    Class.ctor = function (host) {

      var dom = this.dom = document.createElement('div'),
        canvas = this.canvas = document.createElement('canvas'),
        context = this.context = canvas.getContext('2d'),
        width = canvas.width = this.canvasWidth,
        height = canvas.height = this.canvasHeight,
        style1 = dom.style,
        style2 = canvas.style;

      style1.width = style2.width = width + 'px';
      style1.height = style2.height = height + 'px';

      style1.position = 'relative';
      style2.position = 'absolute'
      style2.left = style2.top = '50%';
      style2.marginLeft = -(width >> 1) + 'px';
      style2.marginTop = -(height >> 1) + 'px';

      dom.appendChild(canvas);

      if (host)
      {
        host.appendChild(dom);
      }

      canvas.ondragstart = canvas.onselectstart = function () {

        return false;
      };

      this.sprites = [];
      this.frames = { length: 1, 0: new Frame(this) };
    };



    /**
     * 球场类型 full:全场 half-up:上半场 half-down:下半场
     */
    defineProperty(this, 'type', {

      get: function () {

        return this.__type || 'full';
      },

      set: function (value) {

        this.__type = value;
        this.__background = null;

        reset_size(this, this.scale);
      }
    });


    /**
     * 控件宽度
     */
    defineProperty(this, 'width', {

      get: function () {

        return this.dom.style.width;
      },

      set: function (value) {

        this.__background = null;
        this.dom.style.width = value > 0 ? value + 'px' : value;
      }
    });


    /**
     * 控件高度
     */
    defineProperty(this, 'height', {

      get: function () {

        return this.dom.style.height;
      },

      set: function (value) {

        this.__background = null;
        this.dom.style.height = value > 0 ? value + 'px' : value;
      }
    });


    /**
     * 缩放比例
     */
    defineProperty(this, 'scale', {

      get: function () {

        return this.__scale || 1;
      },

      set: function (value) {

        value = +value || 1;

        if (value <= 0)
        {
          value = 1;
        }

        if (this.__scale !== value)
        {
          reset_size(this, this.__scale = value);
        }
      }
    });


    function reset_size(stage, scale) {

      var canvas = stage.canvas,
        style = canvas.style,
        width = stage.canvasWidth,
        height = stage.type === 'full' ? stage.canvasHeight : stage.canvasHalfHeight;

      canvas.width = width;
      canvas.height = height;

      width = width * scale | 0;
      height = height * scale | 0;

      style.width = width + 'px';
      style.height = height + 'px';
      style.marginLeft = -(width >> 1) + 'px';
      style.marginTop = -(height >> 1) + 'px';
    };



    /**
     * 是否设计模式
     */
    defineProperty(this, 'designMode', {

      get: function () {

        return this.__designMode || false;
      },

      set: function (value) {

        design(this, this.__designMode = !!value);
      }
    });


    /**
     * 是否支持历史记录(undo, redo)
     */
    defineProperty(this, 'history', {

      get: function () {

        return this.__history || false;
      },

      set: function (value) {

        var stack = this.historyStack;

        if (this.__history = !!value)
        {
          if (!stack)
          {
            (this.historyStack = []).index = 0;
          }
        }
        else
        {
          this.historyStack = null;
        }
      }
    });



    /**
     * 是否正在播放
     */
    defineProperty(this, 'isPlaying', {

      get: function () {

        return this.__animate > 0;
      }
    });



    this.__index = 0;

    /**
     * 当前帧索引
     */
    defineProperty(this, 'index', {

      get: function () {

        return this.__index;
      },
      set: function (value) {

        value |= 0;

        if (value < 0)
        {
          value = 0;
        }
        else if (value >= this.frames.length)
        {
          value = this.frames.length - 1;
        }

        this.__index = value;
        this.selectedItem = null;

        if ((value = this.historyStack) && value.length > 0)
        {
          value.length = value.index = 0;
          raiseHistoryChange(this);
        }

        if (value = this.onframechange)
        {
          value.call(this);
        }
      }
    });


    /**
     * 当前帧
     */
    defineProperty(this, 'currentFrame', {

      get: function () {

        return this.frames[this.__index];
      }
    });


    /**
     * 当前选中项
     */
    defineProperty(this, 'selectedItem', {

      get: function () {

        return this.__selectedItem || null;
      },

      set: function (value) {

        value = value || null;

        if (this.__selectedItem !== value)
        {
          raiseSelectedChange(this, value);
        }
      }
    });



    function design(stage, enabled) {

      var canvas = stage.canvas;

      canvas.stage = enabled ? stage : null;
      canvas.onmousedown = enabled ? onmousedown : null;
      canvas.onmousemove = enabled ? onmousemove : null;
      canvas.ondblclick = enabled ? ondblclick : null;

      document.removeEventListener('mouseup', onmouseup);

      if (enabled)
      {
        document.addEventListener('mouseup', onmouseup);
      }
    };


    function onmousedown(event) {

      var stage = this.stage,
        frame = stage.currentFrame,
        point = event_point(stage, event),
        start = mousedown,
        item,
        points;

      // 点击生成线条
      if (item = addingLine)
      {
        points = item.points;

        if (start.move)
        {
          start.move = 0;
          points.splice(-2, 2);

          if (points[points.length - 2] !== point.x || points[points.length - 1] !== point.y)
          {
            points.push(point.x, point.y);
          }
        }
        else if (!points.length)
        {
          stage.startBuffer(item);

          if (start.enabled)
          {
            start.enabled = 0;
            start.stage = start.frame = start.selected = null;
          }

          if (stage.selectedItem)
          {
            raiseSelectedChange(stage, null);
          }

          points.push(point.x, point.y);
        }
      }
      else // 检测是否调整线条
      {
        if ((item = stage.selectedItem) && item instanceof Line && (start.line = item.pointAt(point.x, point.y)) >= 0)
        {
          start.selected = item;
          start.points = item.points.slice(0);
          start.location = [item.points[start.line], item.points[start.line + 1]];
        }
        else
        {
          item = stage.hitTest(point.x, point.y);

          if (start.selected = item)
          {
            raiseSelectedChange(stage, item);
          }
          else
          {
            if (stage.selectedItem)
            {
              raiseSelectedChange(stage, null);
              stage.render();
            }

            return;
          }

          start.line = -1;
          start.location = start.points = item.location(frame);
        }

        start.move = 0;
        start.enabled = 1;
        start.stage = stage;
        start.frame = frame;
        start.clientX = event.clientX;
        start.clientY = event.clientY;

        stage.startBuffer(item);
        stage.renderBuffer(item);
      }
    };


    function onmousemove(event) {

      var stage = this.stage,
        start = mousedown,
        item,
        point;

      if (item = addingLine)
      {
        if (item.points.length > 0)
        {
          if (start.move)
          {
            item.points.splice(-2, 2);
          }
          else
          {
            start.move = 1;
          }

          point = event_point(stage, event);
          item.points.push(point.x, point.y);
          stage.renderBuffer(item);
        }
      }
      else if (start.enabled && (item = start.selected))
      {
        var scale = stage.scale,
          distanceX = (event.clientX - start.clientX) / scale | 0,
          distanceY = (event.clientY - start.clientY) / scale | 0;

        if (!start.move)
        {
          if (distanceX < -2 || distanceX > 2 || distanceY < -2 || distanceY > 2)
          {
            start.move = 1;
          }
          else
          {
            return;
          }
        }

        if (start.line < 0)
        {
          item.dragMove(start.location, distanceX, distanceY, start.frame);
        }
        else
        {
          item.points[start.line] = start.location[0] + distanceX;
          item.points[start.line + 1] = start.location[1] + distanceY;
          item.points.controls = null;
        }

        stage.renderBuffer(item);
      }
    };


    function onmouseup() {

      var start = mousedown;

      if (!addingLine && start.enabled)
      {
        var stage = start.stage,
          item = start.selected;

        if (start.move)
        {
          push_history(stage, [31, item, start.points, item.location(start.frame)]);
        }

        stage.renderBuffer(item);
        stage.closeBuffer();

        start.enabled = start.move = 0;
        start.stage = start.frame = start.selected = null;
      }
    };


    function ondblclick() {

      var line = addingLine;

      if (line)
      {
        var stage = this.stage;

        addingLine = null;

        if (mousedown.move)
        {
          mousedown.move = 0;
          line.points.splice(-2, 2);
        }

        stage.renderBuffer(line);
        stage.closeBuffer();

        push_history(stage, [12, line]);

        if (stage.onaddedline)
        {
          stage.onaddedline(line);
        }

        return line;
      }
    };


    /**
     * 取消线条绘制
     */
    this.cancelLine = function () {

      var line = addingLine;

      if (line)
      {
        var any = this.currentFrame;

        addingLine = null;

        if (any && (any = any.lines) && any[any.length - 1] === line)
        {
          any.pop();
        }

        mousedown.move = 0;

        this.closeBuffer();
        this.render();

        return line;
      }
    };



    /**
     * 在帧队列的最后添加帧
     */
    this.appendFrame = function () {

      var frames = this.frames,
        frame = new Frame(this),
        source = frames[frames.length].points,
        target = frame.points,
        value;

      for (var name in source)
      {
        if ((value = source[name]) && value.slice)
        {
          target[name] = value.slice(0);
        }
      }

      return frames[frames.length++] = frame;
    };


    /**
     * 在当前帧后插入新的帧
     */
    this.insertFrame = function () {

      var frame = new Frame(this),
        source = this.currentFrame.points,
        target = frame.points,
        value;

      for (var name in source)
      {
        if ((value = source[name]) && value.slice)
        {
          target[name] = value.slice(0);
        }
      }

      splice.call(this.frames, this.index + 1, 0, frame);

      return frame;
    };


    /**
     * 移除指定帧或当前帧
     * @param {*} frame  要移除的帧
     */
    this.removeFrame = function (frame) {

      var frames = this.frames,
        index = frame >= 0 ? frame : frame.index;

      if (index >= 0 && (frame = splice.call(frames, index, 1)[0]))
      {
        frame.dispose();

        if (this.__index === index)
        {
          if (!frames[index] && !frames[--index])
          {
            frames.length = 1;
            frames[index = 0] = new Frame(this);
          }

          this.index = index;
        }
      }
    };



    /**
     * 是否可以回退
     */
    this.canUndo = function () {

      var stack = this.historyStack;
      return stack ? stack.index > 0 : false;
    };


    /**
     * 是否可以重做
     */
    this.canRedo = function () {

      var stack = this.historyStack;
      return stack ? stack.index < stack.length : false;
    };


    /**
     * 回退
     */
    this.undo = function () {

      var stack = this.historyStack,
        frame = this.currentFrame,
        points = frame.points,
        command,
        item;

      if (stack && stack.index > 0)
      {
        command = stack[--stack.index];

        switch (command[0])
        {
          case 11: // add sprite
            this.sprites.pop();
            break;

          case 12: // add line
            frame.lines.pop();
            break;

          case 21: // remove sprite
            item = command[2];
            points[item.id] = command[3];
            this.sprites.splice(command[1], 0, item);
            break;

          case 22: // remove line
            item = command[2];
            points[item.id] = command[3];
            frame.lines.splice(command[1], 0, item);
            break;

          case 31: // move
            item = command[1];

            if (item instanceof Line)
            {
              item.points = command[2];
            }
            else
            {
              points[item.id] = command[2];
            }
            break;
        }

        if (command[0] !== 31)
        {
          this.selectedItem = null;
        }

        raiseHistoryChange(this);
        this.render();
      }
    };


    /**
     * 重做
     */
    this.redo = function () {

      var stack = this.historyStack,
        frame = this.currentFrame,
        points = frame.points,
        command,
        item;

      if (stack && stack.index < stack.length)
      {
        command = stack[stack.index++];

        switch (command[0])
        {
          case 11: // add sprite
            item = command[1];
            points[item.id] = command[2];
            this.sprites.push(item);
            break;

          case 12: // add line
            frame.lines.push(command[1]);
            break;

          case 21: // remove sprite
            this.sprites.splice(command[1], 1);
            break;

          case 22: // remove line
            frame.lines.splice(command[1], 0);
            break;

          case 31: // move
            item = command[1];

            if (item instanceof Line)
            {
              item.points = command[3];
            }
            else
            {
              points[item.id] = command[3];
            }
            break;
        }

        if (command[0] !== 31)
        {
          this.selectedItem = null;
        }

        raiseHistoryChange(this);
        this.render();
      }
    };


    function push_history(stage, command) {

      var stack = stage.historyStack;

      if (stack)
      {
        if (stack.index < stack.length - 1)
        {
          stack.splice(stack.index);
        }

        stack.push(command);
        stack.index = stack.length;

        raiseHistoryChange(stage);
      }
    };


    function raiseHistoryChange(stage) {

      var fn = stage.onhistorychange;

      if (fn)
      {
        fn.call(stage);
      }
    };



    this.render = function (frame, context, type) {

      context = context || this.context;

      context.drawImage(this.__background || this.__render_background(), 0, 0);

      if (frame || (frame = this.currentFrame))
      {
        frame.render(context, type || 3);
      }

      if (this.selectedItem)
      {
        renderItem(context, frame, this.selectedItem, 2);
      }
    };


    function renderItem(context, frame, item, tag) {

      var points;

      if (item instanceof Line)
      {
        if (tag & 1)
        {
          item.render(context);
        }

        if (tag & 2)
        {
          item.renderSelected(context);
        }
      }
      else if (frame && (points = frame.points[item.id]))
      {
        if (tag & 1)
        {
          item.render(context, points[0], points[1]);
        }

        if (tag & 2)
        {
          item.renderSelected(context, points[0], points[1]);
        }
      }
    };


    /**
     * 开启缓存渲染
     * @param {*} item  排除的目标对象
     */
    this.startBuffer = function (item) {

      var frame = this.currentFrame;

      if (frame)
      {
        var canvas = this.canvas,
          context = this.context;

        context.drawImage(this.__background || this.__render_background(), 0, 0);

        this.__buffer2 = null;

        if (item instanceof Line)
        {
          frame.render(context, 3, item);
          this.__buffer1 = context.getImageData(0, 0, canvas.width, canvas.height);
        }
        else
        {
          frame.render(context, 1, item);
          this.__buffer1 = context.getImageData(0, 0, canvas.width, canvas.height);

          if (frame.lines.length > 0)
          {
            // 使用canvas缓存线条, toDataURL会卡顿, putImageData会冲掉背景
            canvas = this.__buffer2 = canvas.cloneNode();
            context = canvas.getContext('2d');

            frame.render(context, 2, item);
          }
        }
      }
    };


    /**
     * 缓存渲染
     * @param {*} item  缓存渲染的目标
     */
    this.renderBuffer = function (item) {

      var context = this.context,
        any;

      if (any = this.__buffer1)
      {
        context.putImageData(any, 0, 0);
      }

      if (item)
      {
        renderItem(context, this.currentFrame, item, this.selectedItem === item ? 3 : 1);
      }

      if (any = this.__buffer2)
      {
        context.drawImage(this.__buffer2, 0, 0);
      }
    };


    /**
     * 关闭缓存渲染
     */
    this.closeBuffer = function () {

      this.__buffer1 = this.__buffer2 = null;
    };



    /**
     * 刷新画布
     */
    this.update = function () {

      this.render(this.currentFrame);
      raiseStateChange(this, this.index);
    };



    // 获取鼠标事件相关的坐标位置
    function event_point(stage, event) {

      var scale = stage.scale,
        rect = stage.canvas.getBoundingClientRect(),
        x = event.clientX - rect.left | 0,
        y = event.clientY - rect.top | 0;

      if (scale !== 1)
      {
        x = x / scale;
        y = y / scale;
      }

      return { x: x, y: y };
    };



    /**
     * 命中检测
     * @param {*} x  x坐标
     * @param {*} y  y坐标
     */
    this.hitTest = function (x, y) {

      var frame = this.currentFrame;

      if (frame)
      {
        var sprites = this.sprites,
          list = frame.points,
          index = sprites.length - 1,
          item,
          any;

        while (item = sprites[index--])
        {
          if ((any = list[item.id]) && item.hitTest(x, y, any[0], any[1]))
          {
            return item;
          }
        }

        if ((list = frame.lines) && list[0])
        {
          index = list.length - 1;

          while (item = list[index--])
          {
            if (item.hitTest(x, y))
            {
              return item;
            }
          }
        }
      }
    };



    // 播放的帧偏移位置
    this.__offset = 0;


    // 播放动画
    function animate(stage, frame) {

      var next = frame.next;

      if (next)
      {
        var fn = (function () {

          var context = this.context;

          context.drawImage(this.__background || this.__render_background(), 0, 0);

          frame.animate(context, next.points, this.__offset / 100);

          if ((this.__offset += 2) <= 100)
          {
            this.__animate = setTimeout(fn, 20);
          }
          else
          {
            this.moveTo(this.index + 1, 1);

            if (next.pause)
            {
              this.render(next);
            }
            else
            {
              animate(this, next);
            }
          }

        }).bind(stage);

        stage.__animate = setTimeout(fn, 20);
        raiseStateChange(stage, frame.index);
      }
      else
      {
        stage.__animate = stage.__offset = 0;
      }
    };


    /**
     * 播放
     */
    this.play = function (frame) {

      if ((frame || (frame = this.currentFrame)) && frame.next)
      {
        if (this.__animate)
        {
          clearTimeout(this.__animate);
          this.__animate = 0;
        }

        animate(this, frame);
        raiseStateChange(this, this.index);
      }
    };


    /**
     * 暂停
     */
    this.pause = function () {

      if (this.__animate)
      {
        clearTimeout(this.__animate);

        this.__animate = 0;
        raiseStateChange(this, this.index);
      }
    };



    function raiseSelectedChange(stage, item) {

      var fn = stage.onselectedchange;

      stage.__selectedItem = item;

      if (fn && typeof fn === 'function')
      {
        fn.call(stage, {
          stage: stage,
          selectedItem: item
        });
      }
    };


    function raiseStateChange(stage, index) {

      var fn = stage.onstatechange;

      if (fn && typeof fn === 'function')
      {
        var play = stage.__animate > 0,
          next = index < stage.frames.length - 1;

        fn.call(stage, {

          play: !play && next,
          pause: play,
          first: index > 0,
          previous: index > 0,
          next: next,
          last: next
        });
      }
    };



    /**
     * 移动到第一帧
     */
    this.first = function () {

      return this.moveTo(0);
    };


    /**
     * 移动到上一帧
     */
    this.previous = function () {

      return this.moveTo(this.index - 1);
    };


    /**
     * 移动到下一帧
     */
    this.next = function () {

      return this.moveTo(this.index + 1);
    };


    /**
     * 移动到最后一帧
     */
    this.last = function () {

      return this.moveTo(this.frames.length - 1);
    };


    /**
     * 移到到指定索引的帧
     * @param {*} index 索引
     */
    this.moveTo = function (index, type) {

      var frames = this.frames,
        frame;

      if (this.__animate)
      {
        clearTimeout(this.__animate);
        this.__animate = 0;
      }

      this.__offset = 0;

      if (frame = frames[index])
      {
        this.render(frame, 0, type);

        raiseStateChange(this, this.index = index);

        return frame;
      }

      raiseStateChange(this, this.index = 0);

      return frames[0] || null;
    };



    /**
     * 添加元素
     * @param {*} type  元素类型
     *
     * @param {*} properties  元素属性
     */
    this.addElement = function (type, x, y, properties) {

      var sprite = new (registry_sprites[type] || registry_sprites.offense)(),
        frames = this.frames;

      if (properties)
      {
        for (var name in properties)
        {
          sprite[name] = properties[name];
        }
      }

      this.sprites.push(sprite);

      x |= 0;
      y |= 0;

      for (var i = this.currentFrame.index, l = frames.length; i < l; i++)
      {
        frames[i].points[sprite.id] = [x, y];
      }

      push_history(this, [11, sprite, [x, y]]);

      return sprite;
    };


    /**
     * 添加线条
     * @param {*} type  线条类型
     * @param {*} properties  线条属性
     */
    this.addLine = function (shape, properties) {

      var line = new Line();

      line.shape = shape || 'l1';

      if (properties)
      {
        for (var name in properties)
        {
          line[name] = properties[name];
        }
      }

      this.currentFrame.lines.push(line);
      return addingLine = line;
    };


    /**
     * 导出图片
     * @param {*} frame  要导出的帧
     * @param {*} scale  缩放比例
     */
    this.toDataURL = function (frame, scale) {

      if (frame || (frame = this.currentFrame))
      {
        var canvas = this.__canvasBuffer || (this.__canvasBuffer = this.canvas.cloneNode()),
          context = canvas.getContext('2d');

        if (!scale || scale < 0)
        {
          scale = 1;
        }

        canvas.width = this.canvas.width * scale;
        canvas.height = this.canvas.height * scale;

        context.save();
        context.scale(scale, scale);

        this.render(frame, context);

        context.restore();

        return canvas.toDataURL('image/png');
      }
    };



    function compute_keys(sprites) {

      var keys = Object.create(null);

      for (var i = sprites.length; i--;)
      {
        keys[sprites[i].id] = 1;
      }

      return keys;
    };


    /**
     * 序列化当前战术板为json字符串
     */
    this.serialize = function () {

      var array = ['{'],
        sprites = this.sprites,
        frames = this.frames,
        keys = compute_keys(sprites);

      if (this.type !== 'full')
      {
        array.push('"type":"', this.type, '",');
      }

      array.push('"sprites":[');

      for (var i = 0, l = sprites.length; i < l; i++)
      {
        array.push(i > 0 ? ',{' : '{');

        sprites[i].serialize(array);

        array.push('}');
      }

      array.push('],"frames":[');

      for (var i = 0, l = frames.length; i < l; i++)
      {
        array.push(i > 0 ? ',{' : '{');

        frames[i].serialize(array, keys);

        array.push('}');
      }

      array.push(']}');

      return array.join('');
    };


    /**
     * 从指定的json字符串或对象加载战术板
     * @param {*} json json字符串或对象
     */
    this.deserialize = function (json) {

      var registry = registry_sprites,
        frames = this.frames,
        sprites = this.sprites,
        list,
        item;

      this.clear();

      if (typeof json === 'string')
      {
        json = JSON.parse(json);
      }

      if (json.type)
      {
        this.type = json.type;
      }

      if (list = json.sprites)
      {
        for (var i = 0, l = list.length; i < l; i++)
        {
          item = new (registry[list[i].sprite] || registry.offense)();
          item.deserialize(list[i]);

          sprites.push(item);
        }
      }

      if (list = json.frames)
      {
        frames[0].dispose();
        frames[frames.length = 0] = null;

        for (var i = 0, l = list.length; i < l; i++)
        {
          item = new Frame(this);
          item.deserialize(list[i]);

          frames[frames.length++] = item;
        }
      }
    };



    function remove_spirte(stage, sprite) {

      var sprites = stage.sprites;

      for (var i = sprites.length; i--;)
      {
        if (sprites[i] === sprite)
        {
          var id = sprite.id,
            points = stage.currentFrame.points;

          sprites.splice(i, 1);
          push_history(stage, [21, i, sprite, points[id]]);

          delete points[id];

          return true;
        }
      }
    };


    function remove_line(stage, line) {

      var lines = stage.currentFrame.lines;

      for (var i = lines.length; i--;)
      {
        if (lines[i] === line)
        {
          lines.splice(i, 1);
          push_history(stage, [22, i, line]);

          return true;
        }
      }
    };


    /**
     * 移除指定元素或线条
     * @param {*} item  要移除的元素或线条
     */
    this.remove = function (item) {

      if (item)
      {
        if (this.selectedItem === item)
        {
          this.selectedItem = null;
        }

        return (item instanceof Line ? remove_line : remove_spirte)(this, item) || false;
      }

      return false;
    };


    /**
     * 清空战术板
     */
    this.clear = function () {

      var frames = this.frames,
        index = 0,
        any;

      while (any = frames[index++])
      {
        any.dispose();
      }

      this.pause();
      this.sprites.length = this.__offset = 0;

      if (any = this.historyStack)
      {
        any.length = any.index = 0;
      }

      splice.call(frames, 0);

      frames[0] = new Frame(this);
      frames.length = 1;

      this.index = 0;
    };


  });



  /**
   * 蓝球战术板类
   */
  exports.BasketBallPlaybook = Stage.extend(function (Class) {



    this.canvasWidth = 500;


    this.canvasHeight = 825;


    this.canvasHalfHeight = 475;



    this.__render_background = function () {

      var canvas = this.__background || (this.__background = this.canvas.cloneNode()),
        context = canvas.getContext('2d');

      context.save();

      try
      {
        var type = this.type,
          height = type.indexOf('half') >= 0 ? 475 : 825;

        context.lineWidth = 2;
        context.fillStyle = '#F6C87C';
        context.fillRect(0, 0, 500, height);

        context.strokeStyle = '#FFFFFF';

        switch (type)
        {
          case 'half-up':
            draw_half(context);
            break;

          case 'half-down':
            context.translate(0, 475);
            context.scale(1, -1);

            draw_half(context);
            break;

          default:
            draw_half(context);
            context.stroke();

            context.translate(0, 826);
            context.scale(1, -1);

            draw_half(context);
            break;
        }

        context.stroke();
      }
      finally
      {
        context.restore();
      }

      return canvas
    };


    // 画球场的一半
    function draw_half(context) {

      var PI = Math.PI;

      context.beginPath();

      // 上大半圆
      context.moveTo(406, 63);
      context.arc(250, 100, 156, 0, PI);
      context.lineTo(94, 63);

      // 上蓝板
      context.moveTo(228, 93);
      context.lineTo(273, 93);

      // 上蓝圈
      context.moveTo(255, 100);
      context.arc(250, 100, 5, 0, PI * 2);

      // 上圆
      context.moveTo(295, 208);
      context.arc(250, 208, 45, 0, PI * 2);

      // 上梯形
      context.moveTo(175, 63);
      context.lineTo(205, 208);
      context.lineTo(295, 208);
      context.lineTo(325, 63);

      // 外框
      context.moveTo(63, 63);
      context.rect(63, 63, 375, 350);

      // 中圆
      context.moveTo(205, 413);
      context.arc(250, 413, 45, PI, 0);
    };



  });




});

